# queue.py: Stores received packets in a queue and sends them out later
import sys
import threading
import time
import socket
from optparse import OptionParser
import Queue
import random
import csv


class PacketQueue():
  def __init__(self, dest_addr, dest_port, local_port, alpha_low, alpha_high):
    self.dest = (dest_addr, dest_port)
    self.local = ('0.0.0.0', local_port)
    self.csv_name = 'Queue_%s.csv' % time.strftime('%H%M%S')
    try:
      self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      self.listen_sock.bind(self.local)
    except socket.error:
      # There might be already another instance of queue running
      self.local = ('0.0.0.0', (local_port - 2000))
      self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      self.listen_sock.bind(self.local)
      self.csv_name = 'Queue2_%s.csv' % time.strftime('%H%M%S')
    print 'Listening port bound to %d' % self.local[1]
    self.outgoing_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    self.listen_thread = None
    self.packet_queue = Queue.Queue()
    self.alpha_low = alpha_low
    self.alpha_high = alpha_high
    self.print_lock = threading.Lock()
    self.csv_writer = csv.writer(open(self.csv_name, 'w'))
    self.csv_writer.writerow([
      'Packet Direction', 'Time Stamp', 'Sequence Number', 'Source', 'Queue Length', 'Alpha',
      'Delay'
    ])
    
  def _listen_runner(self):
    while True:
      # Listen for a new packet
      data, source_addr = self.listen_sock.recvfrom(1024)
      data, listen_addr = data.split(' ')
      listen_host, listen_port = listen_addr.split(':')
      source_host, source_port = source_addr
      addr = (source_host, listen_port)
      piecewise_fcn = random.randint(self.alpha_low, self.alpha_high);
      delay = random.expovariate(piecewise_fcn)
      self.packet_queue.put((data, addr, delay))
      with self.print_lock:
        print 'recv\t%f\t%s\tqueue:%d\talpha:%d' % (time.time(), data, self.packet_queue.qsize(), piecewise_fcn)
        self.csv_writer.writerow([
          'receive', time.time(), data, str(addr), self.packet_queue.qsize(), piecewise_fcn,
          delay
        ])
    
  def run(self):
    """Runs the main program"""
    
    # Start listening
    print 'Start the listener...'
    self.listen_thread = threading.Thread(target=self._listen_runner)
    self.listen_thread.daemon = True
    self.listen_thread.start()
    
    # Wait for packets to arrive in queue, wait Q seconds, then sends that packet
    while True:
      # This is a blocking get, will wait until there is a packet in queue
      data, addr, Q_n = self.packet_queue.get(True)  
      time.sleep(Q_n)
      host, port = addr
      msg = '%s %s:%s' % (data, str(host), str(port))
      self.outgoing_sock.sendto(msg, self.dest)
      with self.print_lock:
        print 'send\t%f\t%s\t%s' % (time.time(), msg, Q_n)
        self.csv_writer.writerow([
          'send', time.time(), data, str(addr), self.packet_queue.qsize(), 'N/A', Q_n
        ])

if __name__ == '__main__':
  # Parse arguments [dest_addr=localhost] [dest_port=9002] [local_port=9001] [a=500]
  parser = OptionParser()
  parser.add_option("-d", "--dest_addr", dest="dest_addr", default='127.0.0.1',
                    help="Destination IP address")
  parser.add_option("-p", "--dest_port", dest="dest_port", default=9002, type='int',
                    help="Destination port number")
  parser.add_option("-l", "--local_port", dest="local_port", default=9001, type='int',
                    help="Local (listening) port number")
  parser.add_option("-a", "--alpha_low", dest="alpha_low", default=1, type='float',
                    help="a(t) constant range low (in packets per second). Q_n = 1 / alpha.")
  parser.add_option("-b", "--alpha_high", dest="alpha_high", default=10, type='float',
                    help="a(t) constant range high (in packets per second). Q_n = 1 / alpha.")                    
  (options, args) = parser.parse_args()
  
  # Create a new "PacketQueue" object.  Its called PacketQueue because theres another module called Queue
  print 'Binding sockets...'
  queue = PacketQueue(options.dest_addr, options.dest_port, options.local_port, options.alpha_low, options.alpha_high)
  
  # Run forever
  queue.run()